﻿using System;
using Common.Logging;
using SimpleServiceBus.Bus;
using SimpleServiceBus.Bus.ControlMessages;
using SimpleServiceBus.Bus.MessageManagement;
using SimpleServiceBus.Bus.MessageManagement.MessageHandling;
using SimpleServiceBus.Endpoint.Metadata;
using SimpleServiceBus.Messages.Management.Monitoring;
using SimpleServiceBus.Messages.Subscriptions;
using SimpleServiceBus.Utilities;

namespace SimpleServiceBus.Endpoint
{
    public class MessagingEndpoint : BackgroundServiceBase, IMessagingEndpoint
    {
        private EndpointDescription _metadata;
        private EndpointOfflineEventArgs _pendingOfflineReason;

        public event EventHandler<MessageProcessingErrorEventArgs> MessageProcessingException;

        public MessagingEndpoint()
        {
            LocalServices = new EndpointServiceManager(this);
        }

        protected EndpointOfflineNotification PendingOfflineNotification { get; set; }

        #region IMessagingEndpoint Members

        public EndpointServiceManager LocalServices { get; private set; }

        public IMessageBus MessageBus
        {
            get { return LocalServices.GetService<IMessageBus>(); }
        }

        public event EventHandler<EndpointEventArgs> EndpointOnline;
        public event EventHandler<EndpointOfflineEventArgs> EndpointOffline;

        public EndpointDescription Metadata
        {
            get
            {
                if (_metadata == null)
                    _metadata = new EndpointDescription
                                    {
                                        EndpointID = "UNCONFIGURED_ENDPOINT"
                                    };
                return _metadata;
            }
            set { _metadata = value; }
        }

        public void TakeEndpointOffline(string reason, TimeSpan outageDuration)
        {
            _pendingOfflineReason = new EndpointOfflineEventArgs(Metadata.EndpointID, reason,
                                                                 Convert.ToInt32(outageDuration.TotalMinutes));
            Stop();
        }

        public void Restart(string reason)
        {
            string offlineReason = "Restarting";
            if (!String.IsNullOrEmpty(reason))
                offlineReason += ". Reason: " + reason;

            TakeEndpointOffline(offlineReason, TimeSpan.FromMinutes(1));

            Start();
        }

        #endregion

        protected override void StartService()
        {
            AssertValidConfiguration();

            var msgMgr = LocalServices.GetService<IMessageTypeManagementService>();
            var msgResolver = LocalServices.GetService<IMessageHandlerResolutionService>();

            if (msgMgr != null)
                RegisterMessageTypes(msgMgr);

            if (msgResolver != null)
                RegisterMessageHandlers(msgResolver);

            MessageBus.MessageProcessingException += MessageBus_UnhandledException;
            LocalServices.Start();
            OnEndpointOnline();
        }
        

        protected override void ShutDownService()
        {
            //If a shutdown was initiated manually with a reason provided,
            //we'll use it. Otherwise, we don't know why we're shutting down,
            //so we need a generic reason.
            EndpointOfflineEventArgs eventArgs = _pendingOfflineReason ??
                                                 new EndpointOfflineEventArgs(Metadata.EndpointID,
                                                                              "General Server Shutdown", -1);

            OnEndpointOffline(eventArgs);

            //We don't need this anymore, and we don't
            //want it used again.
            _pendingOfflineReason = null;

            //Let's lock up and go home. See everyone in the morning.
            if (LocalServices != null)
            {
                LocalServices.Stop((service, exception) =>
                                       {
                                           exception =
                                               new Exception(
                                                   "There was a problem shutting down a local service (" +
                                                   service.GetType().Name +
                                                   ") : " + exception.Message, exception);
                                           OnServiceException(exception);

                                           //We always want all services to stop, error
                                           //or not, so we'll suppress the re-throw
                                           //of this exception.
                                           return false;
                                       });
            }
        }

        protected virtual void StartLocalServices(EndpointServiceManager serviceManager)
        {
            serviceManager.Start();
        }

        protected virtual void RegisterMessageHandlers(IMessageHandlerResolutionService resolver)
        {
            resolver.RegisterMessageHandlerAssembly(typeof (MessagingEndpoint).Assembly);
        }

        protected virtual void RegisterMessageTypes(IMessageTypeManagementService mgr)
        {
            mgr.RegisterMessageType(typeof (TimeoutMessage));
            mgr.RegisterMessageAssembly(typeof (SubscriptionMessage).Assembly);
        }

        protected virtual void AssertValidConfiguration()
        {
            if (Metadata == null)
                throw new Exception(
                    "An Endpoint cannot be started without an EndpointID and a ReturnAddress being specified.");

            if (String.IsNullOrEmpty(Metadata.EndpointID))
                throw new Exception("An endpoint cannot be started without and EndpointID being set.");

            if (String.IsNullOrEmpty(Metadata.ReturnAddress) && !(Metadata.SendOnly))
                throw new Exception("An endpoint cannot be started without a valid ReturnAddress.");
        }

        protected virtual void OnEndpointOnline()
        {
            EventHandler<EndpointEventArgs> evt = EndpointOnline;
            if (evt != null)
                evt(this, new EndpointEventArgs(Metadata.EndpointID));
        }

        protected virtual void OnEndpointOffline(EndpointOfflineEventArgs args)
        {
            EventHandler<EndpointOfflineEventArgs> evt = EndpointOffline;
            if (evt != null)
                evt(this, args);
        }

        void MessageBus_UnhandledException(object sender, MessageProcessingErrorEventArgs e)
        {
            EventHandler<MessageProcessingErrorEventArgs> evt = MessageProcessingException;
            if (evt != null)
                evt(this, e);
            else
            {
                ILog log = LogManager.GetLogger(typeof(MessagingEndpoint));
                log.Error("An unhandled error occurred processing a message.", e.Exception);
            }
        }
    }
}